
/**********************************************************************************\
** 文件名称: key.c
** 创建人员: YCG
** 创建日期: 2018-11-03
** 文档描述:
** 版本代号: V0.1
** 版本说明: 初始版本
\********************************************************************************/
#include "key.h"

Key_t * KeyStart = NULL;   //按键列表头部
Key_t * KeyEnd = NULL;     //按键列表尾部

uint8_t KeyNum = 0;        //按键数量

Key_t*  KeyIndex[KEY_NUM_MAX];  //按键索引表

/*******************************************************************************
** 函数名称: KeyAdd
** 功能描述: 按顺序添加按键
** 参数说明: GPIOx:    [输入]       按键引脚端口
**			     GPIO_Pin: [输入]       按键引脚序号
**			     ykeyDefaultState: [输入]    按键未按下时候的状态
**			     yWantToRead: [输入]         想要读取的按键值，可用于裁剪功能
** 返回说明: 按键当前的按键数量
** 创建人员: YCG
** 创建日期: 2019-02-25
********************************************************************************/
uint8_t KeyAdd(GPIO_TypeDef *GPIOx,  uint16_t GPIO_Pin, GPIO_PinState  ykeyDefaultState, uint8_t yWantToRead)
{
    Key_t* yKey;    //按键控制结构体

    GPIO_InitTypeDef GPIO_InitStruct = {0};                //按键初始化结构体

    for(yKey = KeyStart; yKey != NULL; yKey = yKey->Next)   //定位到按键列表的末尾
    {
        if(yKey->GPIO == GPIOx && yKey->GPIO_Pin == GPIO_Pin) {  //如果已经添加过了，就不再添加了
            return NULL;
        }
    }
    yKey = MyMalloc(sizeof(Key_t));    //申请内存空间

    if(yKey != NULL) { //内存申请成功

        KeyIndex[KeyNum] = yKey;           //将按键存入索引数组

        yKey->Next = NULL;                 //新按键在列表得末尾
        yKey->GPIO = GPIOx;                //设置按键的端口
        yKey->GPIO_Pin = GPIO_Pin;         //设置按键的引脚
        yKey->keyDefaultState = ykeyDefaultState; //按键得默认状态
        yKey->Num = ++KeyNum;              //当前按键的序号
        yKey->WantToRead = yWantToRead;    //想要读取的按键值，用于裁剪功能
        yKey->State = KEY_CHECK;           //设置当前按键的检测状态
        yKey->Result = 0;                  //重置按键结果
        yKey->DoubleCount = 0;             //双击计数清零

        /**将按键的检测值设置为默认值**/
        yKey->ShortMax  = SHORT_MAX_COUNT;     //短按阈值
        yKey->LongMax   = LONG_MAX_COUNT;      //长按阈值
        yKey->MulTap    = MUL_TAP_COUNT;       //多次按键间隔
        yKey->DoubleMax = DOUBLE_MAX_COUNT;    //双击阈值

        GPIO_InitStruct.Pin = GPIO_Pin;            //初始化按键端口
        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
        GPIO_InitStruct.Pull = (ykeyDefaultState == GPIO_PIN_RESET) ? GPIO_PULLDOWN : GPIO_PULLUP; ;
        HAL_GPIO_Init(GPIOx, &GPIO_InitStruct);
    }

    if(KeyEnd != NULL) { //按键列表不为空的话，则已经添加过按键了
        KeyEnd->Next = yKey;   //连接到链表末尾
        KeyEnd = yKey;         //将尾部指针指向新的按键
    } else {             //第一次添加
        KeyStart = yKey; //首部指针指向新添加的按键
        KeyEnd =   yKey; //尾部指针指向新添加的按键
    }
    return KeyNum; //返回当前的按键数量
}

/*******************************************************************************
** 函数名称: KeyScan
** 功能描述: 按键扫描函数， 定时器10ms调用一次
** 参数说明: None
** 返回说明: None
** 创建人员: YCG
** 创建日期: 2018-11-03
********************************************************************************/
#ifdef INC_FREERTOS_H
void KeyScan(TimerHandle_t xTimer)
#else
void KeyScan(void)
#endif
{
    Key_t* yKey;
    for(yKey = KeyStart; yKey != NULL; yKey = yKey->Next)     //检测每一个按键
    {
        //检测此时的按键值
        uint8_t keyState = (HAL_GPIO_ReadPin(yKey->GPIO, yKey->GPIO_Pin) == yKey->keyDefaultState) ? 0 : 1;

        switch(yKey->State)    //判断当前按键处于哪一个检测状态
        {
        case KEY_CHECK :       //1.检测按键按下
            if(keyState)      //如果检测到了按键按下
            {
                yKey->SingleCount = 0;    //按键计时清零
                yKey->State = KEY_BEGIN;  //进入下一个状态
            }
            break;

        case KEY_BEGIN :        //2.确保按键按下了才进入下一个状态，去除了按键抖动
            if(keyState)
            {
                yKey->State = KEY_TIME;//进入下一个状态
            }
            else
            {
                yKey->State = KEY_CHECK; //返回第一个状态
            }
            break;

        case KEY_TIME :       //3.计算按键按下的时间
            if(keyState)      //按下时间计数
            {
                yKey->SingleCount++;   //每进入一次，按键计数加1

                if(yKey->WantToRead & KEY_MUL) {   //打开了连按检测
                    if(yKey->SingleCount > yKey->LongMax ) {  //大于长按时间
                        yKey->State = KEY_PRESS_MULTIPLE;     //进入连按检测
                        yKey->SingleCount = 0;                //清零，连按会重新计数
                    }
                }
            }
            else  //释放掉按键且时间小于长按时间
            {
                yKey->State = KEY_PRESS_SINGLE;     //小于长按的时间进入短按检测
            }
            break;

        case KEY_PRESS_SINGLE:                  //4.检测短按与长按状态释放处理
            //需要检测长按，且长按大于短按时间，才会输出长按结果
            if(yKey->SingleCount > yKey->ShortMax && (yKey->WantToRead & KEY_LONG))
            {
                yKey->Result |= KEY_LONG;               //输出长按
                KeyCallback(yKey);                      //调用回调函数
            } else {

                if(yKey->WantToRead & KEY_DOUBLE) {     //需要检测双击
                    if(yKey->DoubleCount == 0) {        //双击计数等于零，表示当前为双击检测的第一次按下
                        yKey->DoubleCount = 1;          //打开计时，等待下次短按检测
                    } else {
                        yKey->DoubleCount = 0;          //双击计数不等于零，表示当前为双击检测的第二次按下
                        yKey->Result |= KEY_DOUBLE;     //输出双击，超时在switch后处理
                        KeyCallback(yKey);
                    }
                } else {                            //都不满足的时候输出为短按
                    yKey->Result |= KEY_SHORT;          //输出短按
                    KeyCallback(yKey);                  //调用回调函数
                }
            }
            yKey->State = KEY_CHECK;          //本次按键检测完成
            break;

        case KEY_PRESS_MULTIPLE:           //4.检测持续按着按键，每隔一定时间输出一次点击
            if(keyState)                               //连按时间计数
            {
                if((++yKey->SingleCount) >= yKey->MulTap) {   //计时达到间隔时间
                    yKey->SingleCount = 0;             //计时清零
                    yKey->Result |= KEY_MUL;           //设置一次连续点击
                    KeyCallback(yKey);                 //调用回调函数
                }
            }
            else
            {
                yKey->State = KEY_CHECK;    //释放时则返回第一个状态
            }
            break;
        default :
            yKey->State = KEY_CHECK;        //错误状态则返回第一个状态
            break;
        }

        /******双击按键计时，超时则输出为短按******/
        if(yKey->DoubleCount > 0) {            //判断是否已经按下了一次,接应前面的代码
            if((yKey->DoubleCount++) > yKey->DoubleMax) {  //按下超过双击时间间隔
                yKey->DoubleCount = 0;          //将状态设置为没有按下过，以便对下一次进行检测
                yKey->Result |= KEY_SHORT;      //判断为短按
                KeyCallback(yKey);              //调用回调函数
            }
        }
    }
}

/*******************************************************************************
** 函数名称: KeyOnlyRead
** 功能描述: 仅仅读取按键值
** 参数说明: yKeyIndex: [输入] 按键序号
**			 		 result:    [输入] 需要读取的按键值
** 返回说明: 1：有相应的按键动作 0：没有相应的按键动作
** 创建人员: YCG
** 创建日期: 2018-11-03
********************************************************************************/
KeyState KeyOnlyRead(uint8_t yKeyIndex, uint8_t yResult) {

    Key_t *yKey = KeyIndex[yKeyIndex];     //获取按键控制结构体指针
    if(yKey->Result & yResult) {           //读取相应的按键
        return KEY_SET;                      //返回按键按下
    } else {
        return KEY_RESET;                         //返回按键没有按下
    }
}

/*******************************************************************************
** 函数名称: KeyRead
** 功能描述: 读取按键值，并且清除
** 参数说明: yKeyIndex: [输入] 按键序号
**			 		 result:    [输入] 需要读取的按键值
** 返回说明: 1：有相应的按键动作 0：没有相应的按键动作
** 创建人员: YCG
** 创建日期: 2018-11-03
********************************************************************************/
KeyState KeyRead(uint8_t yKeyIndex, uint8_t yResult) {

    Key_t *yKey = KeyIndex[yKeyIndex];     //获取按键控制结构体指针
    if(yKey->Result & yResult) {           //读取相应的按键
        yKey->Result &= ~yResult;          //清零
        return KEY_SET;                      //返回按键按下
    } else {
        return KEY_RESET;                         //返回按键没有按下
    }
}

/*******************************************************************************
** 函数名称: KeyLog
** 功能描述: 打印按键信息
** 参数说明: yKeyIndex: [输入] 按键序号
** 返回说明: None
** 创建人员: YCG
** 创建日期: 2019-04-30
********************************************************************************/
void KeyLog(uint8_t yKeyIndex) {

    Key_t *yKey = KeyIndex[yKeyIndex];     //获取按键控制结构体指针
    KEY_Printf("KEY%2d: ", yKeyIndex);
    if(yKey->Result & KEY_SHORT)       KEY_Printf("%s\r\n", "KEY_SHORT");
    else if(yKey->Result & KEY_LONG)   KEY_Printf("%s\r\n", "KEY_LONG");
    else if(yKey->Result & KEY_MUL)    KEY_Printf("%s\r\n", "KEY_MUL");
    else if(yKey->Result & KEY_DOUBLE) KEY_Printf("%s\r\n", "KEY_DOUBLE");
    else {
        KEY_Printf("%s\r\n", "KEY No Press!");
    }
}

/*******************************************************************************
** 函数名称: KeyCallback
** 功能描述: 按键回调函数
** 参数说明: None
** 返回说明: None
** 创建人员: YCG
** 创建日期: 2018-12-21
********************************************************************************/
__weak  void KeyCallback(Key_t *yKey) {

    KEY_Printf("KEY:%2d 0x%2x\r\n", yKey->Num, yKey->Result);
}


/********************************End of File************************************/
